package worker

import (
	"fmt"
	"time"

	"gitee.com/taotao_today/crontab/crontab/common"
)

// Scheduler 任务调度
type Scheduler struct {
	jobEventChan      chan *common.JobEvent              // etcd任务事件队列
	jobPlanTable      map[string]*common.JobSchedulePlan // 任务调度计划表
	jobExecutingTable map[string]*common.JobExecuteInfo  // 任务执行表
	jobResultChan     chan *common.JobExecuteResult      // 任务结果队列
}

var G_scheduler *Scheduler

// InitScheduler 初始化调度器
func InitScheduler() {
	G_scheduler = &Scheduler{
		jobEventChan:      make(chan *common.JobEvent, 1000),
		jobPlanTable:      make(map[string]*common.JobSchedulePlan, 100),
		jobExecutingTable: make(map[string]*common.JobExecuteInfo, 100),
		jobResultChan:     make(chan *common.JobExecuteResult, 1000),
	}
	// 启动调度协程
	go G_scheduler.scheduleLoop()
	return
}

func (s *Scheduler) handleJobEvent(jobEvent *common.JobEvent) {
	switch jobEvent.EventType {
	case common.JOB_EVENT_SAVE:
		// 保存任务事件
		jobSchedulePlan, err := common.BuildJobSchedulePlan(jobEvent.Job)
		if err != nil {
			return
		}
		s.jobPlanTable[jobEvent.Job.Name] = jobSchedulePlan
	case common.JOB_EVENT_DELETE:
		// 删除任务事件
		if _, jobExisted := s.jobPlanTable[jobEvent.Job.Name]; jobExisted {
			delete(s.jobPlanTable, jobEvent.Job.Name)
		}
	case common.JOB_EVENT_KILL:
		// 取消掉Command执行，判断任务是否在执行中
		if jobExecuteInfo, jobExecting := s.jobExecutingTable[jobEvent.Job.Name]; jobExecting {
			jobExecuteInfo.CancelFunc()
		}
	}
}

func (s *Scheduler) handleJobResult(result *common.JobExecuteResult) {
	// 删除执行状态
	delete(s.jobExecutingTable, result.ExecuteInfo.Job.Name)
	if result.Err != common.ERR_LOCK_ALREADY_REQUIRED {

	}
	fmt.Println("任务执行完成:", result.ExecuteInfo.Job.Name, string(result.Output), result.Err)
}

// TryStartJob 尝试执行任务
func (s *Scheduler) TryStartJob(jobPlan *common.JobSchedulePlan) {
	// 调度和执行是两件事情
	// 执行的任务可能运行很久，例如：一分钟会调度60次，但是只能执行一次
	// 如果任务正在执行，跳过本次调度
	jobExecuteInfo, jobExecuting := s.jobExecutingTable[jobPlan.Job.Name]
	if jobExecuting {
		// 已经在执行了
		fmt.Println("跳过执行")
		return
	}

	// 构建执行状态信息
	jobExecuteInfo = common.BuildJobExecuteInfo(jobPlan)
	// 保存执行状态
	s.jobExecutingTable[jobPlan.Job.Name] = jobExecuteInfo
	// 执行任务
	fmt.Println("执行任务:", jobExecuteInfo.Job.Name, jobExecuteInfo.PlanTime, jobExecuteInfo.RealTime)
	G_executor.ExecutorJob(jobExecuteInfo)
}

// TrySchedule 重新计算任务调度状态
func (s *Scheduler) TrySchedule() (scheduleAfter time.Duration) {

	// 如果任务表为空，随便睡多久
	if len(s.jobPlanTable) == 0 {
		scheduleAfter = time.Second
		return
	}

	var nearTime *time.Time
	now := time.Now()
	// 遍历所有任务, 过期的任务立即执行
	for _, jobPlan := range s.jobPlanTable {
		// 任务到期
		if jobPlan.NextTime.Before(now) || jobPlan.NextTime.Equal(now) {
			// TODO: 尝试执行任务，有可能上一次任务到期，但是执行还没结束，不一定能启动
			s.TryStartJob(jobPlan)
			// 更新下次执行时间
			jobPlan.NextTime = jobPlan.Expr.Next(now)
		}

		// 统计最近一个要过期的任务时间
		if nearTime == nil || jobPlan.NextTime.After(*nearTime) {
			nearTime = &jobPlan.NextTime
		}
	}

	// 下次调度间隔 (最近要执行的任务调度时间-当前时间)
	scheduleAfter = (*nearTime).Sub(now)
	return
}

// 调度协程
func (s *Scheduler) scheduleLoop() {

	// 初始化一次(1秒)
	scheduleAfter := s.TrySchedule()

	// 调度的延时定时器
	scheduleTimer := time.NewTimer(scheduleAfter)
	for {
		select {
		case jobEvent := <-s.jobEventChan:
			// 对内存中维护的任务列表做增删改查
			s.handleJobEvent(jobEvent)
		case jobResult := <-s.jobResultChan: // 监听任务执行结果
			s.handleJobResult(jobResult)
		case <-scheduleTimer.C: // 最近的任务到期了

		}
		scheduleAfter = s.TrySchedule()
		// 重置调度间隔
		scheduleTimer.Reset(scheduleAfter)
	}
}

// PushJobEvent 推送任务变化事件
func (s *Scheduler) PushJobEvent(jobEvent *common.JobEvent) {
	s.jobEventChan <- jobEvent
}

// PushJobResult 回传任务执行结果
func (s *Scheduler) PushJobResult(jobResult *common.JobExecuteResult) {
	s.jobResultChan <- jobResult
}
